/*
 * Copyright (C) 2011 The Guava Authors
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.google.common.primitives;

import com.google.caliper.BeforeExperiment;
import com.google.caliper.Benchmark;

import java.util.Random;

/**
 * Benchmarks for certain methods of {@code UnsignedLongs}.
 *
 * @author Eamonn McManus
 */
public class UnsignedLongsBenchmark
{
    private static final int ARRAY_SIZE = 0x10000;
    private static final int ARRAY_MASK = 0x0ffff;
    private static final Random RANDOM_SOURCE = new Random(314159265358979L);
    private static final long[] longs = new long[ARRAY_SIZE];
    private static final long[] divisors = new long[ARRAY_SIZE];
    private static final String[] decimalStrings = new String[ARRAY_SIZE];
    private static final String[] binaryStrings = new String[ARRAY_SIZE];
    private static final String[] hexStrings = new String[ARRAY_SIZE];
    private static final String[] prefixedHexStrings = new String[ARRAY_SIZE];

    @BeforeExperiment
    void setUp()
    {
        for (int i = 0; i < ARRAY_SIZE; i++)
        {
            longs[i] = random();
            divisors[i] = randomDivisor(longs[i]);
            decimalStrings[i] = UnsignedLongs.toString(longs[i]);
            binaryStrings[i] = UnsignedLongs.toString(longs[i], 2);
            hexStrings[i] = UnsignedLongs.toString(longs[i], 16);
            prefixedHexStrings[i] = "0x" + hexStrings[i];
        }
    }

    @Benchmark
    long divide(int reps)
    {
        long tmp = 0;
        for (int i = 0; i < reps; i++)
        {
            int j = i & ARRAY_MASK;
            tmp += UnsignedLongs.divide(longs[j], divisors[j]);
        }
        return tmp;
    }

    @Benchmark
    long remainder(int reps)
    {
        long tmp = 0;
        for (int i = 0; i < reps; i++)
        {
            int j = i & ARRAY_MASK;
            tmp += UnsignedLongs.remainder(longs[j], divisors[j]);
        }
        return tmp;
    }

    @Benchmark
    long parseUnsignedLong(int reps)
    {
        long tmp = 0;
        // Given that we make three calls per pass, we scale reps down in order
        // to do a comparable amount of work to other measurements.
        int scaledReps = reps / 3 + 1;
        for (int i = 0; i < scaledReps; i++)
        {
            int j = i & ARRAY_MASK;
            tmp += UnsignedLongs.parseUnsignedLong(decimalStrings[j]);
            tmp += UnsignedLongs.parseUnsignedLong(hexStrings[j], 16);
            tmp += UnsignedLongs.parseUnsignedLong(binaryStrings[j], 2);
        }
        return tmp;
    }

    @Benchmark
    long parseDecode10(int reps)
    {
        long tmp = 0;
        for (int i = 0; i < reps; i++)
        {
            int j = i & ARRAY_MASK;
            tmp += UnsignedLongs.decode(decimalStrings[j]);
        }
        return tmp;
    }

    @Benchmark
    long parseDecode16(int reps)
    {
        long tmp = 0;
        for (int i = 0; i < reps; i++)
        {
            int j = i & ARRAY_MASK;
            tmp += UnsignedLongs.decode(prefixedHexStrings[j]);
        }
        return tmp;
    }

    @Benchmark
    int toString(int reps)
    {
        int tmp = 0;
        // Given that we make three calls per pass, we scale reps down in order
        // to do a comparable amount of work to other measurements.
        int scaledReps = reps / 3 + 1;
        for (int i = 0; i < scaledReps; i++)
        {
            int j = i & ARRAY_MASK;
            long x = longs[j];
            tmp += UnsignedLongs.toString(x).length();
            tmp += UnsignedLongs.toString(x, 16).length();
            tmp += UnsignedLongs.toString(x, 2).length();
        }
        return tmp;
    }

    private static long random()
    {
        return RANDOM_SOURCE.nextLong();
    }

    // A random value that cannot be 0 and that is unsigned-less-than or equal
    // to the given dividend, so that we don't have half of our divisions being
    // trivial because the divisor is bigger than the dividend.
    // Using remainder here does not give us a uniform distribution but it should
    // not have a big impact on the measurement.
    private static long randomDivisor(long dividend)
    {
        long r = RANDOM_SOURCE.nextLong();
        if (dividend == -1)
        {
            return r;
        }
        else
        {
            return UnsignedLongs.remainder(r, dividend + 1);
        }
    }
}
